嗨各位好,我是 Robin,
今天來分享 JS 中幾個內建值型別,
希望我們能夠一起完全的理解且正確地善用他們~
JS 的陣列特別的是不像其他強制施加型別的語言,他是可以儲存任何型別的容器,不管是 string
、object
、number
或著是另外一個 array
。
const a = ['1',{test:1},1,[0,1,2,3]];
a.length; // 4
a[0]; // '1'
a[1]; // {test:1}
a[2]; // 1
a[3]; // [0,1,2,3]
在 JS 中你不必預先設定好 array
的大小,直接宣告就可以用了。
const a = [];
a.length; // 0
a[0] = 0;
a[1] = 1;
a[2] = 2;
a[3] = 3;
a.length; //4
console.log(a); // [0,1,2,3]
const a = [];
a[0] = 1;
a[2] = 2;
a[4] = 4;
console.log(a[1]); // undefined
a.length; 5
這是可以 work 的,但是這樣可能會造成維護的人困擾,因為空著的是 undefined
,可是這跟直接使用 a[1] = undefined
沒有區別 ,嚴重可能會令你的同伴找 bug 找到瘋掉。
在我們使用陣列時如果 key
是一個 string
並且能夠被強制轉型成十進位的 number
那 JS 就會強制將它變成 number
當成索引。
const a = [];
a['8'] = 8
a.length; // 9
a[8]; // 8
因為他是根本還是 object
所以其實你還是可以透過 string
當 key
值來新增特性,但他並不會計算在 array
的長度裡。
然後...拜託先不要 xDD,如果你想要新增這樣的資料請使用 object
,把陣列留給使用索引的孩子。
const a = [1,2,3,4];
a['robin'] = '666';
a.length; // 4
a.robin; // '666'
什麼是類陣列?
就是長得跟陣列很像,但是他可能是串列(List)之類的,例如你在 DOM 操作之後回傳的其實是串列。
但是畢竟他不是真的 Array
,所以你需要轉乘真正的 Array
才能夠使用 indexOf
、concat
、forEach
等的操作。
可以藉由使用 slice
或是 Array.from
來轉成真的 Array
。
對於字串很多看法是由字元組成的 Array
, 但是實質上他是,因為 JS 的 String 是不可變的,但是 Array
是可變的
var a = 'foo';
var b = ['f','o','o'];
a[1]='g';
b[1]='g';
console.log(a); // 'foo'
console.log(b); // [['f','g','o']
在 JS 中我們可以直接更改陣列的內容,但是字串無法。
如果你很常需要將字串當陣列,那你應該實際直接將他們儲存為 array
而不是字串,當你需要用到 string
表示時再加上 join('')
就好了。
JS 只有數值型別,並沒有整數的型別,所以 111
和 111.0
對 JS 來說都是個整數。
十進位值前面的部分0
他是選擇性的
const a = 0.42;
const b = .42;
a === b // true
後面的小數也是
const a = 42.0;
const b = 42;
const c = 42.0000000;
a === b // true
b === c // true
但以上都盡量不要使用,如果你不想要人要別人看你的 Code 會頭痛的話xD
.
這個運算子是一個有效的數值字元,他會先被解讀成 number 字面的一部分,而不是解讀成特性存取器。
來看一下以下的範例
69.toFixed(2)
猜猜結果是什麼~
69.00
錯!
你會獲得一個 SyntaxError
但是當你使用以下的方式就可以正常 Work
69..toFixed(2)
69 .toFixed(2)
(69).toFixed(2)
Why?
因為如果 .
這個特性運算子不存在就無法取用 .toFixed
。
但還是不要使用有爭議的方式啦,畢竟程式碼是要靠人維護的 xD
主要是用來表示比較大的數字
const a = 1e4 // 10000 代表 1*10^4
const b = 1e8 // 100000000 代表 1*10^8
例如 二進制、八進制、十六進制,這些格式都可以使用,但是注意盡量使用小寫的前綴,例如 0x
、0o
以及 0b
,來避免 0
和 O
這類型造成的混淆。
為什麼蛋疼...?
來!
0.1 + 0.2 //?
請各位評評理正常小學算法,大家都知道是 0.3
對吧!?
實際上...
對!沒錯你沒看錯。
對於使用 IEEE 754 的所有語言都是。
很多人提出過替代的方法,
但是總是沒有被選用,可能不是像我們用說的用想的那麼容易,
如果可以那早就已經被修復了,甚至其他語言也會使用,
但是事實就是沒有 QQ
由此可知,我們不能相信 Number
的值是精確的,所以我們都不要使用它。
...
當然不可能啊 xDDD
我們可以放心的使用 JS 來處理整數,但是不要大於數百萬或數兆的數字。
另外如果如果真的有需要使用 JS 來比較兩種值的大小,
最廣為接受的是約整誤差
,也就是容許很小很小的誤差值作為容許值。
ES6
中已定義好這個常數 Number.EPSILON
其值為 2^-52
使用方式如下
function closeEqual(n1, n2) {
return Math.abs(n1 - n2) < Number.EPSILON;
}
const a = 0.1 + 0.2;
const b = 0.3;
closeEqual(a, b); // true
closeEqual(0.0000001, 0.0000002); // false
能夠被表示的最大浮點數值是 1.798e+308
,並已經被定義好為 Number.MAX_VALUE
。
極小值則是 5e-324
,非常趨近於零而且不是負的。
那知道了蛋疼的小數之後,整數的安全範圍在 ES6
中已經定義了
最大值定義為了 Number.MAX_SAFE_INTEGER
即 9007199254740991
。
最小值 Number.MIN_SAFE_INTEGER
即 -9007199254740991
。
Number.isInteger(69); // true
Number.isInteger(69.0000); // true
Number.isInteger(69.0003); // false
Number.isSafeInteger(Number.MAX_SAFE_INTEGER); // true
Number.isSafeInteger(Number.MIN_SAFE_INTEGER); // true
Number.isSafeInteger(Math.pow(2,53)); // false
undefined
的型別中,只會有一個值就是 undefined
。null
的型別中,只會有一個值就是 null
。
(聽起來有點繞口)undefined
和 null
中細微的差異在於說
null
是一個空值或是曾經有一個值但是現在沒有了undefined
是缺少值或還沒有值其實在非 strict
模式中你可以將 undefined
指定一個值給 undefined
這個全域變數。
聽起來就是一個非常母湯的做法,千萬不要這樣做 xD
就是指 NaN
啦,之前提過 NaN
就是指 Not a number
,
什麼時候會出現?
比方說 const a = 1 / 'foo'; // NaN
還記得上次型態 typeof NaN // 'number'
嗎?
這一直以來都還免強說得過去,但是...
NaN === NaN // false
NaN !== NaN // true
他不等於自己...
那我該如何測試他是不是 NaN
?
可以使用 isNaN(..)這個內建的函式工具,這樣就解決了。
const a = 1 / 'foo';
windows.isNaN(a) // true
...
才怪他還有另外一個 Bug
就是...
windows.isNaN('foo') // true
而在 ES6 終於提供了一個替代的工具就是 Number.isNaN(..),能讓你安全無疑慮的使用 xD
Number.isNaN('foo') // false
感謝天感謝地。
對!就是你小學所學的那種無限
const a = 1/0 // Infinity
const b = -1/0 // -Infinity
主要可以拿到無限這個值有兩種方法,一種就是上面看到的除以 0
,
另一種則是溢位( overflow )
在 ES6 中定義了正無限為 Number.POSITIVE_INFINITY
,負無限為Number.NEGATIVE_INFINITY
。
無限除以無限會是什麼?
可能是 1
或是 無限
,聽起來合理。
但是在 JS 他會回你 NaN
正的有限 number 除以 無限
會是 0
負的有限 number 除以 無限
則會是 -0
負零是什麼鬼!? 馬上來說
JS 提供了兩種零,一種是正零另一種則是負零,
除了直接指定 -0 之外,你也可以透過以下的方式產生
const a = 0/-5 // -0
const a = 0*-5 // -0
但是如果你將他 strinify
他卻會回傳給你 "0"
,如果反向從 string
轉回來就正常。
比較運算也是會一個宇智波騙你
const a = 0/-5 // -0
const b = 0 //0
a == b; // true
-0 == 0; // true
a === b; // true
-0 === 0; //true
-0 < 0; // false
a < b; // false
那如果想分辨 0
和 -0
該怎麼做?
可以使用以下的方式先判斷是否為0
,再除以 n
比對是否是-Infinity
。
function isNegZero(n){
n = Number(n)
return (n === 0) && (1/n === -Infinity)
}
isNegZero(-0); // true
isNegZero(0 / -5 ); // true
isNegZero(0); // false
但這些我個人覺得都不算重點,重點是為何需要?
書中寫道是因為除了學術上的需求外,特定的應用會需要,比方說動畫美影格的移動速率,就會以 number 的正負號來表示移動方向,如果沒有了正負號就好像失去了方向 xD
大概懂他的感覺,但是我沒有實際遇過所以感觸不深。
在上面我們提到了 NaN
不等於自己, 0
和 -0
又相等,那我們有沒有一個工具可以測試兩個值是否絕對相等
?
有的!
ES6 中出現了一個工具叫做 Object.is(..)
,並且不會發生剛剛上面所說的那些特殊的情況 XD
const a = 1 / 'foo';
const b = -2 * 0;
Object.is(a, NaN); // true
Object.is(b, 0); // true
Object.is(b, 0); // false
由此可知就算是使用 ===
,也無法避免掉剛剛所說的那些特殊值,如果要比較特殊的情況使用 Object.is(..)
可能相對會來的安全些。
JS 的基本型別中永遠都是藉由值的拷貝來指定或是傳遞,
看不懂沒關係我們看看範例
const a = 1;
const b = a;
b++;
console.log(a) // 1
console.log(b) // 2
主要是因為前面所說的 因為
1
是一個純量的基型值,a
拿到了最初的一份拷貝,而b
拿到的是該值的另一份拷貝,所以b
的改變不會造成a
的值產生變動。
複合值的 object 永遠都是會在指定或傳遞時建立參考的一份拷貝。
來也來看看範例
const a = [0,1,2,3];
const b = a;
b.push(4)
console.log(a) // [0,1,2,3,4]
console.log(b) // [0,1,2,3,4]
這邊的
a
和b
都是對同一個共有值[0,1,2,3]
的個別參考,
不是a
或是b
擁有該值,
都不是!
而是他們一起去參考[0,1,2,3]
,所以會造成單邊修改其實就是改動該參考值所以才會兩邊同時改變。
這邊提供一下我自己在網路上找到的文章。
JavaScript 的「傳值」與「傳址」
我個人覺得滿不錯的推薦一下~
以上是今天的內容
有點多,這篇我花比前幾篇還多的時間去研究和思索,
感覺力量提升(?
如果內容有錯或是觀念有錯麻煩再跟我說,有可能是我理解錯誤
歡迎討論
感謝你
我們明天見
你所不知道的 JS|導讀,型別與文法 (You Don't Know JS: Up & Going)